#include <ff_wrapper_gl.h>
#include <cstring>
#include <system_log.h>
#include <gles32_loader.h>

namespace FFSdkUnity::OpenGL {

bool FFWrapperGL::Init() noexcept
{
    if (ffInstance_ != FF_NULL_HANDLE) {
        return true;
    }

    // We will try to load FF via GLES loader, so init GLES loader first
    if (!FFSdkUnity::Platform::GLES32::Init()) {
        if (!Destroy()) {
            FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't destroy wrapper resources (branch 1)");
        }
        return false;
    }

    auto unloadGles = []() noexcept {
        if (!FFSdkUnity::Platform::GLES32::Destroy()) {
            FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't unload GLES");
        }
    };

    if (!LoadFrameFlow()) {
        FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't LoadFrameFlow");
        if (!Destroy()) {
            FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't destroy wrapper resources (branch 1)");
        }
        unloadGles();
        return false;
    }

    if (!FindSDKFunctionsLib()) {
        FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't FindSDKFunctionsLib");
        if (!Destroy()) {
            FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't destroy wrapper resources (branch 2)");
        }
        unloadGles();
        return false;
    }

    FFInstanceCreateInfo createInfo{};
    createInfo.sType = FF_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    createInfo.pNext = nullptr;
    createInfo.majorVersion = FF_API_VERSION_MAJOR;
    createInfo.minorVersion = FF_API_VERSION_MINOR;
    createInfo.pfnApiLoaderFunction = FFSdkUnity::Platform::GLES32::GetLoader();
    createInfo.pfnErrorReportCallback = &FFWrapperGL::SDKErrorCallback;
    createInfo.pUserData = this;

    ffInstance_ = FrameFlow_Create(&createInfo);
    ffInstanceIsExternal_ = false;
    unloadGles();

    if (ffInstance_ == FF_NULL_HANDLE) {
        FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't create FrameFlow instance");

        if (!Destroy()) {
            FFLOGE("Sdk::Unity::FFWrapperGL::Init - Can't destroy wrapper resources (branch 3)");
        }
        return false;
    }

    FFLOGI("Sdk::Unity::FFWrapperGL::Init - Success 1");
    return true;
}

bool FFWrapperGL::Destroy() noexcept
{
    if (ffInstance_ != FF_NULL_HANDLE && !ffInstanceIsExternal_) {
        FrameFlow_Destroy(ffInstance_);
    }
    ffInstance_ = FF_NULL_HANDLE;

    ResetSDKFunctions();
    return frameflowLib_.Unload();
}

FFResult FFWrapperGL::OnActivate() const noexcept
{
    FFLOGI("Sdk::Unity::FFWrapperGL::OnActivate - Start");
    if (!IsInit()) {
        FFLOGI("Sdk::Unity::FFWrapperGL::OnActivate - NOT_INITIALIZED");
        return FFResult::FF_ERROR_NOT_INITIALIZED;
    }
    FFResult ret = FrameFlow_Activate(ffInstance_);
    FFLOGI("Sdk::Unity::FFWrapperGL::OnActivate - return (int)%d", ret);
    return ret;
}

FFResult FFWrapperGL::OnDeactivate() const noexcept
{
    return IsInit() ? FrameFlow_Deactivate(ffInstance_) : FFResult::FF_ERROR_NOT_INITIALIZED;
}

FFBool FFWrapperGL::OnIsActive() const noexcept
{
    return IsInit() ? FrameFlow_IsActive(ffInstance_) : FF_FALSE;
}

FFResult FFWrapperGL::OnSetPredictionAlgorithm(FFPredictionAlgorithm algorithm, bool extrapolation) const noexcept
{
    if (!IsInit()) {
        return FFResult::FF_ERROR_NOT_INITIALIZED;
    }

    FFPredictionAlgorithmInfo info{};
    info.sType = FF_STRUCTURE_TYPE_PREDICTION_ALGORITHM_INFO;
    info.pNext = nullptr;
    info.algorithm = algorithm;
    info.extrapolation = extrapolation;

    return FrameFlow_SetPredictionAlgorithm(ffInstance_, &info, nullptr);
}

FFResult FFWrapperGL::OnSetResolution(uint32_t width, uint32_t height) const noexcept
{
    FFExtent2D resolution{};
    resolution.width = width;
    resolution.height = height;
    return IsInit() ? FrameFlow_SetResolution(ffInstance_, &resolution) : FFResult::FF_ERROR_NOT_INITIALIZED;
}

FFResult FFWrapperGL::OnSetResolution2(FFResolutionInfo2 const *pResolutionInfo) const noexcept
{
    return IsInit() ? FrameFlow_SetResolution2(ffInstance_, pResolutionInfo) : FFResult::FF_ERROR_NOT_INITIALIZED;
}

FFResult FFWrapperGL::OnSetCvvZSemantic(FFCvvZSemantic semantic) const noexcept
{
    return IsInit() ? FrameFlow_SetCvvZSemantic(ffInstance_, semantic) : FFResult::FF_ERROR_NOT_INITIALIZED;
}

FFResult FFWrapperGL::OnSetStencilRemapInfo(FFStencilValueSemantic const *pRemapTable) noexcept
{
    if (!IsInit()) {
        return FFResult::FF_ERROR_NOT_INITIALIZED;
    }

    FFStencilRemapInfo info{};
    info.sType = FF_STRUCTURE_TYPE_STENCIL_REMAP_INFO;
    info.pNext = nullptr;
    auto const memcpyResult = memcpy(info.remapTable, pRemapTable, sizeof(info.remapTable));
    if (memcpyResult == nullptr) {
        return FFResult::FF_ERROR_INTERNAL_ERROR;
    }

    return FrameFlow_SetStencilRemapInfo(ffInstance_, &info);
}

FFResult FFWrapperGL::OnProvidePerFrameData(FFPerFrameData const *pPerFrameData) noexcept
{
    return IsInit() ? FrameFlow_ProvidePerFrameData(ffInstance_, pPerFrameData) : FFResult::FF_ERROR_NOT_INITIALIZED;
}

FFResult FFWrapperGL::OnDrawFrame(int32_t target) noexcept
{
    if (!IsInit()) {
        return FFResult::FF_ERROR_NOT_INITIALIZED;
    }

    FFDrawFrameInfo info{};
    info.sType = FF_STRUCTURE_TYPE_DRAW_FRAME_INFO;
    info.target = target < 0 ? -target : target;
    info.targetIsTexture = target < 0;
    info.doDraw = FF_TRUE;

    FFResult const result = FrameFlow_DrawFrame(ffInstance_, &info);

    if (result != FF_SUCCESS) {
        FFLOGW("Sdk::Unity::FFWrapperGL::OnDrawFrame - FrameFlow_DrawFrame returned %d", static_cast<int>(result));
    }

    return result;
}

bool FFWrapperGL::FindSDKFunctionsLib() noexcept
{
#define FF_FUNCTION(func)                                                                                              \
    if ((func) = reinterpret_cast<decltype(func)>(frameflowLib_.GetFunction(#func)); !(func)) {                        \
        FFLOGE("Sdk::Unity::FFWrapperGL::FindSDKFunctionsLib: function %s not found", #func);                          \
        return false;                                                                                                  \
    }

#include <ff_function/ff_gl_functions.in>

#undef FF_FUNCTION

    return true;
}

void FFWrapperGL::ResetSDKFunctions() noexcept
{
#define FF_FUNCTION(func) func = nullptr;
#include <ff_function/ff_gl_functions.in>
#undef FF_FUNCTION
}

bool FFWrapperGL::IsInit() const noexcept
{
    return ffInstance_ != FF_NULL_HANDLE;
}

bool FFWrapperGL::LoadFrameFlow() noexcept
{
    FFLOGI("FFWrapperVK::LoadFrameFlow - start");
    if (frameflowLib_.IsLoaded()) {
        return true;
    }
    return frameflowLib_.Load("libframeflow.so", FF::Platform::LibraryLoadPolicy::LOCAL);
}

void FFAPI_PTR FFWrapperGL::SDKErrorCallback(char const *message, void * /*userData*/)
{
    FFLOGE("Sdk::Unity::FFWrapperGL::SDKErrorCallback - FrameFlow SDK: %s", message);
}

} // namespace FFSdkUnity::OpenGL
